#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "server.h"
#include "service.h"

#include "daemon.h"


int mk_fd_set(service_t* head, fd_set* read_set);

struct sockaddr_in disconnect_addr;


/**
 *  START SERVICES
**/
void start_services(service_t** head) {
  service_t* service;

  for (service = *head; service != NULL; service = service->next) {
    char* serv_str;//debug
    struct sockaddr_in serv_addr;
    int opt;

    errno = 0;

    /* creazione del socket */
    service->sock = socket( PF_INET,
                    (service->transport == TCP)? SOCK_STREAM : SOCK_DGRAM,
                    (service->transport == TCP)? IPPROTO_TCP : IPPROTO_UDP);
    if (errno != 0) {
      remove_service(head, service);
      continue;
    }

    /* concessione della stessa porta TCP a diverse connessioni */
    if (service->transport == TCP) {
      opt = 1;
      setsockopt(service->sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      if (errno != 0) {
        remove_service(head, service);
        continue;
      }
    }

    /* preparazione della struttura con l'indirizzo server */
    memset(&serv_addr, 0x00, sizeof(serv_addr));
    serv_addr.sin_family        = PF_INET;
    serv_addr.sin_port          = htons(service->port);
    serv_addr.sin_addr.s_addr   = INADDR_ANY;

    /* collegamento del socket alla risorsa fisica (bind) */
    bind(service->sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if (errno != 0) {
      remove_service(head, service);
      continue;
    }

    /* per i server TCP, ascolto di richieste di connessioni */
    if (service->transport == TCP) {
      listen(service->sock, LISTEN_BACKLOG);
      if (errno != 0) {
        remove_service(head, service);
        continue;
      }
    }
    serv_str = service_to_str(service);//debug
    fprintf(stderr, "%s -> READY\n", serv_str);//debug
    free(serv_str);//debug
  }

  /* preparazione della struttura per effettuare la "disconnect" UDP */
  memset(&disconnect_addr, 0x00, sizeof(disconnect_addr));
  disconnect_addr.sin_family = PF_UNSPEC;
  return;
}


/**
 *  HANDLE SERVER TERMINATION
**/
void handle_server_term(service_t* head) {
  pid_t pid;
  char* serv_str;//debug

  pid = wait(NULL);

  if (pid > 0) {

    service_t *serv;

    /* reperimento del servizio relativo al server appena terminato */
    serv = get_service_by_pid(head, pid);

    if (serv != NULL) {

      serv_str = service_to_str(serv);//debug
      fprintf(stderr, "[%d] died (%s -> READY)\n", serv->pid, serv_str);//debug

      /* reset del campo pid, per includere il socket di servizio nella *
       * prossima select()                                              */
      serv->pid = NO_PID;

      /* per servizi UDP, "disconnessione" del socket */
      if (serv->transport == UDP) {
        connect(serv->sock, (struct sockaddr*)&disconnect_addr, sizeof(disconnect_addr));
        if (errno != 0) {
          err(EXIT_FAILURE, "un-connecting UDP socket %d for service %s",
              serv->sock, service_to_str(serv));
        }
      }
      free(serv_str);//debug
    }
  }
}


/**
 *  SERVE
**/
void serve(service_t* head) {
  fd_set read_set;
  int maxfd, fd_count;

  /* select() */
  do {
    fprintf(stderr, "\nIdle on select() ..\n"); // debug
    maxfd = mk_fd_set(head, &read_set);
    errno = 0;
    fd_count = select(maxfd, &read_set, NULL, NULL, NULL);
  } while (errno == EINTR);
  if (errno != 0) {
    err(EXIT_FAILURE, "on select()");
  }

  fprintf(stderr, "select(): %d socket(s) to process ..\n", fd_count); // debug
  /* processamento per ogni socket restituito dalla select() */
  for (; maxfd >= 0; --maxfd) {
    if (FD_ISSET(maxfd, &read_set) != 0) {
      service_t* serv;
      char* serv_str;//debug

      /* reperimento del servizio a partire dal socket */
      serv = get_service_by_sock(head, maxfd);

      serv_str = service_to_str(serv);//debug
      fprintf(stderr, ".. processing %s\n", serv_str); // debug
      free(serv_str);//debug

      /* lancio del server relativo alla richiesta */
      launch_server(head, serv);
    }
  }
  return;
}


/**
 *  MAKE FD SET
**/
int mk_fd_set(service_t* head, fd_set* read_set) {
  service_t* serv;
  int maxfdpl;

  FD_ZERO(read_set);
  maxfdpl = -1;

  for (serv = head; serv != NULL; serv = serv->next) {
    /* ogni servizio con pid non settato viene incluso nel set */
    if ((serv->pid == NO_PID) && (serv->sock >= 0)) {
      if (serv->sock > maxfdpl) {
        maxfdpl = serv->sock;
      }
      FD_SET(serv->sock, read_set);
    }
  }
  return maxfdpl + 1;
}

